W tym module spotkaliśmy się ze sporą ilością nowych informacji. Zmieniły one sposób, w jaki korzystamy z Reacta, którego też dopiero co poznaliśmy. Dlatego pozostała część modułu jest opcjonalna – wykonaj ją, kiedy zechcesz. Pozwoli Ci ona przećwiczyć zarówno korzystanie z Reacta, jak i z Reduksa.
Omówimy sobie zawartość pliku store.js, a następnie zaimplementujemy przeciąganie kart, które pozwoli nie tylko na zmianę ich kolejności, ale też przenoszenie do innych kolumn. Zaczniemy jednak od paru propozycji samodzielnej kontynuacji pracy nad tym projektem.
Dalszy rozwój projektu
Pierwszą rzeczą, którą warto by było zrobić, jest zrefaktorowanie większości komponentów klasowych na funkcyjne. Po wprowadzeniu Reduksa, ze stanu komponentu korzystają jedynie Creator i Search. Wszystkie inne komponenty powinny być funkcyjne, więc do zmiany są: List, Column i Card.
Nie wymieniliśmy tutaj komponentu App, ponieważ w następnym module będziemy wykorzystywać jego stan.
Kolejne zmiany, które możemy wprowadzić, skupione będą na rozszerzaniu funkcjonalności aplikacji:
- Obecnie komponent
Search tylko filtruje karty wyświetlane w kolumnach – możesz zaimplementować nowy komponent SearchResults, który byłby wyświetlany zamiast list w App, i zawierałby wszystkie znalezione karty. Każda z nich musiałaby zawierać informację o liście i kolumnie, w których się znajduje. Dzięki temu wyniki wyszukiwania byłyby wyświetlane jako jedna, spójna lista wyników.
- W poprzednim module, jako zadanie dla chętnych, proponowaliśmy stworzenie guzika hamburgera, które po kliknięciu wyświetli menu zawierające wszystkie listy. Teraz możesz rozwinąć ten komponent tak, aby po kliknięciu któregoś tytułu listy w tym menu, na stronie była wyświetlona wyłącznie ta lista. W tym samym menu warto dodać też link "All", który wyświetli wszystkie listy. Pisząc tę funkcjonalność, możesz wzorować się na tym, w jaki sposób komponent
Search filtruje wyświetlane karty.
- Analogicznie do omówionego poniżej przeciągania kart w kolumnach (i pomiędzy nimi), możesz zaimplementować przeciąganie kolumn w listach (i pomiędzy nimi).
To tylko kilka propozycji – jeśli za szybko sobie z nimi poradzisz, z pewnością wymyślisz kolejne sposoby rozwoju projektu. ;)
Implementacja przenoszenia kart
Wcześniej w tym module wspomnieliśmy o tym, że można by było dodać funkcjonalność przeciągania kart pomiędzy kolumnami. To brzmi ciekawie, ale też jest to całkiem skomplikowane przedsięwzięcie. Dlatego, zamiast zostawiać Cię z poczuciem niedosytu czy frustracją przy próbie samodzielnej implementacji, przygotowaliśmy dla Ciebie instrukcję wdrożenia tej funkcjonalności.
Naszym celem jest umożliwienie przeciągania kart – zarówno w ramach jednej kolumny, aby zmieniać ich kolejność, jak i przenoszenia kart do innych kolumn. Ta funkcjonalność będzie się składała z dwóch części:
- Warstwa widoku, czyli obsługa przeciągania kart.
- Warstwa logiki, czyli sposób zapisywania kolejności kart.
Oczywiście, będą one ze sobą połączone – w momencie przeniesienia karty widok będzie informował logikę o tym, którą kartę przenieść, oraz skąd i dokąd ma być przeniesiona. Jak już zapewne się domyślasz, warstwa logiki będzie zrealizowana z wykorzystaniem reduksowych akcji i obsłużona w reducerze.
Warstwa widoku
Do przeciągania kart pomiędzy kolumnami wykorzystamy plugin stworzony przez firmę Atlassian, czyli właściciela takich rozwiązań jak Jira, BitBucket, czy Trello. Wspominamy o tym, ponieważ możemy śmiało zakładać, że rozwiązania stworzone i publikowane przez gigantów branży, takich jak Facebook czy Atlassian, będą sprawnie działały, a do tego będą opatrzone niezłą dokumentacją.
Zaczynamy od zainstalowania pluginu react-beautiful-dnd za pomocą komendy:
npm install -S react-beautiful-dnd
Ucząc się Reacta poznajemy i tworzymy komponenty – nic więc dziwnego, że pluginy tworzone z myślą o Reakcie również będziemy wykorzystywać jako komponenty. W przypadku react-beautiful-dnd będziemy mieli do czynienia z trzema komponentami:
DragDropContext – to kontener, który będzie zawierał całą część aplikacji, w której funkcjonalność drag-n-drop ma być dostępna (niekoniecznie dla wszystkich elementów),
Droppable – to kontener, zawierający elementy, które można przenosić,
Draggable – to element, który można przeciągać (od angielskiego drag) i upuszczać (drop) w kontenerach Droppable.
W naszym przypadku poszczególne karty będą Draggable, a kolumny – Droppable. Co w takim razie powinno znaleźć się w DragDropContext? Kod aplikacji przewiduje, że kolejne listy będą wyświetlać się jedna pod drugą i nic nie stoi na przeszkodzie, aby przeciągać karty pomiędzy kolumnami należącymi do różnych list. W takim razie wszystkie listy muszą znaleźć się w DragDropContext.
Komponent DragDropContext
Otwórz plik App.js i znajdź ten fragment kodu:
{lists.map(listData => (
<List key={listData.id} {...listData} />
))}
Musimy zamknąć go w komponencie DragDropContext. Najpierw jednak zaimportujemy go z pakietu react-beautiful-dnd:
import {DragDropContext} from 'react-beautiful-dnd';
A teraz zmienimy znaleziony wcześniej fragment kodu JSX na następujący:
<DragDropContext onDragEnd={moveCardHandler}>
{lists.map(listData => (
<List key={listData.id} {...listData} />
))}
</DragDropContext>
Jak widzisz, dodaliśmy dla tego komponentu jednego propsa – onDragEnd – który jest obowiązkowy. Musi zawierać funkcję, wykonywaną po zakończeniu przeciągania elementu Draggable. Dodajmy więc funkcję, która będzie wyświetlała w konsoli obiekt otrzymywany z pluginu. Najlepiej będzie dodać tę funkcję w metodzie render, tuż przed linią zawierającą słowo return.
const moveCardHandler = result => {
console.log(result);
};
Na tym etapie możemy sprawdzić w narzędziach developerskich, na zakładce React, że komponent DragDropContext został dodany do App. Warto też sprawdzić, czy w konsoli nie pojawiły się żadne błędy.
Komponent Droppable
Teraz czas na drugi z komponentów tego pluginu – otwórz plik Column.js i zaimportuj w nim komponent Droppable. Następnie znajdź poniższy fragment kodu JSX, analogiczny do zmienianego przed chwilą w App.js:
{cards.map(cardData => (
<Card key={cardData.id} {...cardData} />
))}
Tym razem jednak zmian będzie troszkę więcej, ale wykonamy je po kolei, aby się nie pogubić. Najpierw wrapujemy ten fragment kodu w komponent Droppable, zawierający propsa droppableId:
<Droppable droppableId={id}>
{cards.map(cardData => (
<Card key={cardData.id} {...cardData} />
))}
</Droppable>
Props droppableId ma mieć wartość równą id karty. Wynika to z tego, że plugin potrzebuje mieć unikalny identyfikator kontenera zawierającego elementy, które można przenosić. Nie będzie to dla nas problemem, ponieważ i tak każda karta w naszej aplikacji ma swoje id. Abyśmy jednak mogli go użyć w tym miejscu, musimy dodać je do destrukturyzacji propsów w pierwszej linii metody render. Nie zapomnij też dodać go do propTypes z typem PropTypes.string.
Komponent Droppable oczekuje pojedynczego elementu, więc musimy mapowanie cards zamknąć w divie. Od razu dodamy mu klasę, bo pewnie będziemy chcieli dodać dla niego jakieś style.
<Droppable droppableId={id}>
<div className={styles.cards}>
{cards.map(cardData => (
<Card key={cardData.id} {...cardData} />
))}
</div>
</Droppable>
To jeszcze nie koniec – ten plugin wymaga, aby zawartość tego komponentu była zamknięta w funkcji, otrzymującej jeden argument. Zgodnie z dokumentacją, nazwiemy go provided.
<Droppable droppableId={id}>
{provided => (
<div className={styles.cards}>
{cards.map(cardData => (
<Card key={cardData.id} {...cardData} />
))}
</div>
)}
</Droppable>
Ostatni krok – musimy wykorzystać argument provided do przekazania kilku propsów głównemu elementowi w funkcji, czyli divowi. Większość z nich znajduje się w obiekcie provided.droppableProps, ale oprócz rozpakowania tego obiektu, musimy też dodać atrybut ref.
<Droppable droppableId={id}>
{provided => (
<div
className={styles.cards}
{...provided.droppableProps}
ref={provided.innerRef}
>
{cards.map(cardData => (
<Card key={cardData.id} {...cardData} />
))}
{provided.placeholder}
</div>
)}
</Droppable>
Nie tłumaczymy dokładnie, do czego służą te propsy, ponieważ nie musimy tego wiedzieć. Na etapie wdrożenia pluginu po prostu podążamy za instrukcją autora pluginu – którą dla Ciebie przełożyliśmy na nasz projekt. ;)
Zanim skończymy pracę nad komponentem Column, dodajemy jeszcze style, które pozwolą na przenoszenie karty do pustej kolumny:
.cards {
padding: ($base-size / 2) 0;
}
Ten komponent jest gotowy, przechodzimy do ostatniego!
Komponent Draggable
Jak już wiesz, ten komponent oznacza element, który będzie przeciągany. Otwieramy w takim razie plik Card.js i importujemy Draggable z react-beautiful-dnd. Do propTypes dodajemy id jako tekst oraz index jako numer.
W metodzie render musimy dodać id i index do destrukturyzacji propsów, a następnie zmienić kod JSX na następujący:
<Draggable draggableId={id} index={index}>
{(provided) => (
<article
className={styles.component}
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
{title}
</article>
)}
</Draggable>
Nie omawiamy go krok po kroku, ponieważ jest zbudowany bardzo podobnie do tego, którego użyliśmy w komponencie List. Są jednak dwie istotne różnice:
index to props, który zawiera numer porządkowy elementu (omówimy go za chwilę),
- wyrażenie
{...provided.dragHandleProps} umieściliśmy w <article>, ponieważ karta ma tylko jeden element, ale gdybyśmy chcieli, żeby tylko fragment karty dało się chwycić i przesunąć, moglibyśmy to wyrażenie przenieść do innego elementu, wewnątrz <article>.
Wróćmy jednak do indeksu – każdy element Draggable musi posiadać numer porządkowy, czyli wiedzieć, na której jest pozycji w swoim rodzicu. Zdecydowaliśmy się, że w naszym projekcie będziemy tę liczbę przechowywać w stanie aplikacji, jako właściwość każdej z kart.
Uzupełnienie stanu początkowego
W takim razie musimy otworzyć plik dataStore.js i do każdej karty dodać właściwość index. Jej wartością musi być liczba, a kolejne karty w tej samej kolumnie mają mieć w tej właściwości kolejne liczby, rozpoczynając od zera. Innymi słowy, pierwsza karta w pierwszej kolumnie będzie miała index: 0, kolejna – 1, a pierwsza karta w następnej kolumnie – również index: 0.
Po tej zmianie możemy już zajrzeć na podgląd naszej aplikacji w przeglądarce – przesuwanie kart już działa... częściowo. Karty można przesuwać za pomocą myszy (a nawet klawiatury, za pomocą klawisza Tab, strzałek oraz spacji). Tuż po upuszczeniu karty, jednak wraca ona na wcześniejszą pozycję. Nie przejmuj się – tak miało być!
Wynika to z tego, że po zakończeniu przeciągania karty, kolumna jest na nowo renderowana, a więc z powrotem wyświetla się to, co znajduje się w stanie aplikacji. Nie mamy jeszcze warstwy logicznej, więc przywracany jest wcześniejszy stan.
To jednak nie problem – najważniejsze, że działa mechanizm drag-n-drop!
Warstwa logiczna
Teraz kiedy możemy już przeciągać karty, wracamy prawie do samego początku implementacji pluginu, kiedy w App.js zdefiniowaliśmy funkcję moveCardHandler. Dodaliśmy do niej na razie console.log, więc możemy teraz sprawdzić w konsoli, jak wygląda obiekt result, czyli argument tej funkcji.
Interesują nas w nim:
- właściwość
draggableId, która zawiera id karty,
- obiekt
source, który zawiera id kolumny oraz indeks karty, zanim została przeniesiona,
- obiekt
destination, analogiczny do source, ale prezentujący docelową lokalizację karty.
Sprawdź, co się stanie, kiedy spróbujesz przenieść kartę poza którąkolwiek kolumnę (np. na tytuł listy), oraz sytuację, w której przesuniesz kartę tylko o parę pikseli, aby upuścić ją w tym samym miejscu, w którym była. W tych sytuacjach nie będziemy chcieli wykonywać żadnej akcji, więc będziemy musieli napisać warunek if, który zignoruje te dwie sytuacje (czyli nie wykona kodu w tym bloku if).
Będziemy też chcieli zreorganizować trochę te informacje, aby zamiast draggableId i droppableId używać naszych kluczy stanu karty: id i columnId. W takim razie zmodyfikujmy funkcję moveCardHandler następująco:
const moveCardHandler = result => {
if(
result.destination
&&
(
result.destination.index != result.source.index
||
result.destination.droppableId != result.source.droppableId
)
){
console.log({
id: result.draggableId,
dest: {
index: result.destination.index,
columnId: result.destination.droppableId,
},
src: {
index: result.source.index,
columnId: result.source.droppableId,
},
});
}
};
Teraz przy przesuwaniu kart w konsoli powinien się pojawiać obiekt ze zdefiniowanymi przez nas kluczami. Użyty przez nas warunek może wydawać się skomplikowany, ale z pewnością poradzisz sobie z jego zrozumieniem. Pamiętaj tylko, że && oznacza "oraz", a || – "lub". Podzieliliśmy ten warunek na kilka linii i dodaliśmy wcięcia, aby łatwiej było go przeczytać – jednak działałby tak samo, gdyby był w całości zapisany w jednej linii.
Mamy już przygotowany obiekt z potrzebnymi nam informacjami – teraz musimy go jakoś wykorzystać.
Dispatchowanie akcji
Ten krok z powodzeniem wykonasz samodzielnie – wystarczy, że w cardsRedux.js dodasz nowy typ akcji (MOVE_CARD) oraz stworzysz dla niego kreator akcji (createAction_moveCard).
Następnie w AppContainer.js musisz dodać funkcję mapDispatchToProps. Powinna ona mapować do propsa moveCard funkcję strzałkową, która przyjmuje jeden argument (payload), a w rezultacie dispatchuje kreator akcji createAction_moveCard z argumentem tej funkcji strzałkowej (payload).
Zanim przejdziemy dalej, wróćmy do App.js i dodajmy props moveCard do definicji typów (PropTypes.func) oraz do destrukturyzacji propsów w metodzie render. Następnie w funkcji moveCardHandler możemy zmienić console.log na moveCard – zamiast wyświetlać stworzony obiekt, będziemy go przekazywać do tego dispatchera.
Logika zmiany indeksów
Teraz czas na gwóźdź programu, czyli obsługę przenoszenia kart w reducerze!
Nie będzie to takie proste, ponieważ nie wystarczy zmienić indeksu przenoszonej karty. Weźmy za przykład sytuację, kiedy mamy pięć kart o indeksach: 0, 1, 2, 3, 4. Jeśli teraz przeniesiemy czwartą kartę (indeks 3) na drugą pozycję, to przed zmianą indeksów ich kolejność będzie: 0, 3, 1, 2, 4. Z tego wynika, że musimy zmienić indeksy trzech kart, tych które miały indeksy 3, 1 i 2, co możemy zapisać w ten sposób: 0, 3=>1, 1=>2, 2=>3, 4. W rezultacie indeksy będą ponownie w postaci 0, 1, 2, 3, 4, czyli będą kolejnymi liczbami, począwszy od zera.
Sprawa komplikuje się jeszcze bardziej, kiedy przenosimy kartę pomiędzy kolumnami – wtedy musimy zmienić indeksy w dwóch kolumnach!
Całe szczęście, możemy skorzystać z pomocy JS-a, ponieważ posiada on wbudowany mechanizm obsługi zmieniających się indeksów – dzieje się to zawsze, kiedy zmieniamy zawartość jakiejkolwiek tablicy. Dlatego w reducerze, w reakcji na MOVE_CARD, stworzymy sobie tymczasową tablicę, i wykorzystamy ją do obliczenia nowych indeksów.
Nowy case w reducerze
Zacznijmy od dodania do reducera w cardsRedux.js, tuż przed default: w switchu, nowego case'a:
case MOVE_CARD: {
return statePart;
}
Na początek będziemy zwracać fragment stanu (otrzymany w argumencie reducera) bez żadnych zmian. Dzięki temu nie będą wprowadzane żadne zmiany w stanie, ale będziemy mogli pisać kod i dodawać console.log, aby sprawdzać, czy tworzony kod zmierza we właściwym kierunku.
Może przykuć Twoją uwagę fakt, że tym razem użyliśmy nawiasów klamrowych { }, mimo że w pozostałych case'ach ich nie używaliśmy. Tym razem jednak będziemy potrzebowali napisać nieco dłuższy kod oraz posługiwać się kilkoma stałymi, więc lepiej będzie zamknąć ten kod w bloku { }.
Tworzymy kilka stałych
Na początek ułatwimy sobie trochę pisanie kodu – wykonamy destrukturyzację obiektu action.payload, aby nie powtarzać wielokrotnie tego wyrażenia. Nasz kod będzie dzięki temu bardziej czytelny.
const {id, src, dest} = action.payload;
Następnie przefiltrujemy stan w poszukiwaniu przenoszonej karty oraz wydobędziemy z wyniku filtrowania pierwszą (i jedyną) kartę, podając indeks [0].
const targetCard = statePart.filter(card => card.id == id)[0];
Będziemy też potrzebować tablicy, zawierającej wszystkie karty w tej kolumnie. Posortujemy je od razu wedle indeksu.
const targetColumnCards = statePart.filter(card => card.columnId == dest.columnId).sort((a, b) => a.index - b.index);
Właśnie ta tablica pomoże nam w uzyskaniu nowych indeksów kart. Zobaczmy, jak ona wygląda, za pomocą tego wyrażenia:
console.log(targetColumnCards.map(card => `${card.index}, title: ${card.title}`));
Dlaczego użyliśmy mapowania?
Jest pewna specyficzna kwestia, jeśli chodzi o console.log. Kiedy wyświetlamy w nim jakiś obiekt, a następnie rozwijamy go, to jego wartość jest pobrana dopiero w momencie rozwinięcia. Zobacz na ten przykład, wykonany w konsoli:
Jak widzisz, mimo że console.log był wykonany, zanim do obiektu cards dodaliśmy nowy element, to po jego rozwinięciu (przy etykiecie original) zobaczymy również ten dodany element. Dlatego używamy mapowania do stworzenia kopii tablicy w momencie wykonania console.log.
Przy okazji, możemy wykorzystać mapowanie do wyświetlenia tylko niezbędnych informacji, co wykonaliśmy w kodzie powyżej.
Blok warunkowy dla przenoszenia w ramach jednej kolumny
Zaczniemy od rozpatrzenia przypadku, gdy kolumna źródłowa i docelowa jest ta sama – czyli nie przenosimy karty pomiędzy kolumnami. Zamieńmy więc wyrażenie return statePart; na następujący blok if...else:
if(dest.columnId == src.columnId){
} else {
return statePart;
}
Zmiana kolejności w tymczasowej tablicy
Wewnątrz bloku if musimy najpierw usunąć przenoszoną kartę (targetCard) z tablicy targetColumnCards, a następnie wstawić ją z powrotem, ale w odpowiednie miejsce (dist.index). Do obu tych operacji wykorzystamy metodę splice.
targetColumnCards.splice(src.index, 1);
targetColumnCards.splice(dest.index, 0, targetCard);
Ta metoda, jako pierwszy argument, przyjmuje indeks elementu. Drugim argumentem jest liczba elementów, które mają być usunięte (rozpoczynając od elementu o indeksie z pierwszego argumentu). Dlatego w pierwszej linii powyższego kodu usuwamy jedną kartę, o indeksie src.index. Pamiętaj, że src bierze się z funkcji moveCardHandler w App.js – to tam przekazujemy do tego obiektu index i columnId, które miała przenoszona karta, zanim została przesunięta.
W drugiej linii wybieramy docelową pozycję, usuwamy zero elementów (ale i tak musimy podać ten argument), a jako trzeci argument podajemy kartę, która ma być wstawiona pod tym indeksem.
Sprawdźmy, jak to wpłynęło na indeksy, przenosząc napisany przed chwilą console.log pod te dwie linie z metodą splice. Jeśli odtworzymy wcześniej opisany przykład z pięcioma kartami, to po przeniesieniu czwartej na 2. pozycję, otrzymamy taki komunikat w konsoli:
Idąc od lewej, widzimy indeks w tablicy targetColumnCards, indeks karty zapisany w stanie aplikacji, oraz jej tytuł. Dotychczasowe indeksy (zapisane w stanie) ułożyły się w sekwencję, o której pisaliśmy wcześniej – 0, 3, 1, 2, 4. Widzimy więc, że indeksy w tablicy targetColumnCards (pierwsza liczba po lewej na powyższym screenie) będą poprawnymi, nowymi indeksami do zapisania w stanie aplikacji.
Zwrócenie nowego stanu
W tym samym bloku warunkowym dodaj:
return statePart.map(card => {
const targetColumnIndex = targetColumnCards.indexOf(card);
if(targetColumnIndex > -1 && card.index != targetColumnIndex){
return {...card, index: targetColumnIndex};
} else {
return card;
}
});
Zwracamy nową tablicę, będącą mapowaniem tablicy statePart, aby zwrócić wszystkie karty zapisane w stanie aplikacji. Dla każdej z nich sprawdzamy jej pozycję w tablicy targetColumnCards.
Jeśli karta została znaleziona oraz jej indeks w tej tablicy różni się od zapisanego w stanie, to zwracamy nowy obiekt karty. Do tego obiektu rozpakowujemy wszystkie właściwości karty oraz ustawiamy (i tym samym nadpisujemy) wartość właściwości index.
Zwróć uwagę, że w tym miejscu nie możemy napisać card.index = ..., ponieważ byłoby to złamanie zasady mówiącej, że reducer nie modyfikuje stanu! Dotyczy ona nie tylko otrzymywanego argumentu (statePart), ale również wszystkich obiektów i tablic, które w nim się znajdują. Właśnie dlatego stworzyliśmy nowy obiekt, do którego rozpakowaliśmy właściwości z card.
Przechodząc do bloku else – czyli przypadku, w którym karty nie ma w targetColumnCards, albo jej indeks się nie zmienił, nie potrzebujemy jej zmieniać – zwracamy obiekt karty bez żadnych zmian.
Po tych zmianach powinna już działać poprawnie funkcjonalność przenoszenia kart w obrębie jednej kolumny. Możesz sprawdzić to w przeglądarce.
Przenoszenie kart pomiędzy kolumnami
Jak już wspomnieliśmy, przy przenoszeniu kart pomiędzy kolumnami będziemy mieli trochę bardziej skomplikowaną sytuację. Będziemy potrzebowali posługiwać się dwiema pomocniczymi tablicami – dla kolumny źródłowej (sourceColumnCards) oraz docelowej (targetColumnCards). W obu tych kolumnach będziemy musieli zmienić indeksy tych kart, którym zmieniły się indeksy. Poza tym, dla przenoszonej karty będziemy też chcieli zmieniać jej właściwość columnId.
W tym celu zamień wyrażenie return statePart; w bloku else na poniższy kod:
let sourceColumnCards = statePart.filter(card => card.columnId == src.columnId).sort((a, b) => a.index - b.index);
sourceColumnCards.splice(src.index, 1);
targetColumnCards.splice(dest.index, 0, targetCard);
console.log('sourceColumnCards:');
console.log(sourceColumnCards.map(card => `${card.index}, title: ${card.title}`));
console.log('targetColumnCards:');
console.log(targetColumnCards.map(card => `${card.index}, title: ${card.title}`));
return statePart.map(card => {
const targetColumnIndex = targetColumnCards.indexOf(card);
if(card == targetCard){
return {...card, index: targetColumnIndex, columnId: dest.columnId};
} else if(targetColumnIndex > -1 && card.index != targetColumnIndex){
return {...card, index: targetColumnIndex};
} else {
const sourceColumnIndex = sourceColumnCards.indexOf(card);
if(sourceColumnIndex > -1 && card.index != sourceColumnIndex){
return {...card, index: sourceColumnIndex};
} else {
return card;
}
}
});
Jak widzisz, ten kod jest podobny do znajdującego się we wcześniejszym bloku if, ale jest bardziej rozbudowany. Dodaliśmy tablicę sourceColumnCards i rozbudowaliśmy logikę w zwracanym mapowaniu statePart. Zawarliśmy też parę wyrażeń console.log, aby wyświetlić w konsoli sposób działania tego bloku.
Szczegółową analizę tego fragmentu kodu pozostawiamy już Tobie. Mamy nadzieję, że w oparciu o wyjaśnienia wcześniejszego kodu, poradzisz sobie z jego zrozumieniem.
Podsumowanie implementacji przenoszenia kart
Jak widzisz, nie było to najprostsze zadanie. O ile sama warstwa wizualna wymagała tylko podążania za instrukcją wykorzystania plugin react-beautiful-dnd, to warstwa logiczna wymagała od nas stworzenia algorytmu, który będzie przeliczał indeksy i zapisywał je. Co więcej, musiał on robić to nie tylko dla przenoszonej karty, ale również dla innych kart, którym zmieniły się pozycje w kolumnach.
Nie przejmuj się, jeśli wydaje Ci się to skomplikowane – jest to zadanie, które byłoby zbyt trudne na tym etapie nauki. Dlatego przeszliśmy wspólnie przez proces wdrożenia tej funkcjonalności. Mamy nadzieję, że pomogło Ci to pogłębić nie tylko rozumienie Reduksa i Reacta, ale też myślenie algorytmiczne.
Poświęć teraz chociaż 15 minut na przemyślenie całej tej implementacji – podział na dwie warstwy i miejsce ich styku; przepływ informacji pomiędzy akcją użytkownika na stronie a logiką reducera; wykorzystanie tablic do wygenerowania nowych indeksów; zmianę danych zapisanych w stanie. Pozwól sobie spokojnie pozostać w tym kodzie przez tych kilka chwil.
Nie zapomnij też pobawić się przenoszeniem kart – spróbuj różnych skrajnych przypadków, których mogliśmy nie przemyśleć. Na przykład – czy zadziała przenoszenie karty do kolumny znajdującej się w innej liście? ;)